#define GLEW_STATIC // Easier debugging
#include <GL/glew.h>
#include <GL/gl.h>
#include <windows.h>  // For MS Windows
#include <GL/glut.h>  // GLUT, includes glu.h and gl.h
#include <SDL/SDL.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <memory>

#include "GameAsset.h"
#include "Md2Asset.h"
#include "CubeAsset.h"
#include "PlaneAsset.h"
#include "BallisticInterpolator.h"
#include "Camera.h"
#include "UserInput.h"

using namespace std;

#define RUN_GRAPHICS_DISPLAY 0x00;

string filename = "data/ogre.md2";
vector<shared_ptr<GameAsset> > assets;

/*
 * SDL timers run in separate threads.  In the timer thread
 * push an event onto the event queue.  This event signifies
 * to call display() from the thread in which the OpenGL 
 * context was created.
 */

/*
 * If you are devving on Windows, put the DLLs in the DLL folder into the same folder
 * as your executable binary file.
 */

Uint32 display(Uint32 interval, void *param) {
	SDL_Event event;
	event.type = SDL_USEREVENT;
	event.user.code = RUN_GRAPHICS_DISPLAY;
	event.user.data1 = 0;
	event.user.data2 = 0;
	SDL_PushEvent(&event);
	return interval;
}

void display() {
  glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  // This O(n + n^2 + n) sequence of loops is written for clarity,
  // not efficiency
  for(auto it : assets) {
    it->update();
  }

  for(auto i : assets) {
    for(auto j : assets) {
      if((i != j) && i->collidesWith(*j)) {
	cout << "We have a collision"  << endl;
      }
    }
  }

  for(auto it : assets) {
    it->draw();
  }
  
  // Don't forget to swap the buffers
  SDL_GL_SwapBuffers();
}

boolean camera_move = false;
boolean debug_mode = false;

int main(int argc, char ** argv) {
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);

	SDL_Surface * surf;
	Uint32 width = 640;
	Uint32 height = 480;
	Uint32 colour_depth = 16; // in bits
	Uint32 delay = 1000/60; // in milliseconds

	// Initialise SDL - when using C/C++ it's common to have to
	// initialise libraries by calling a function within them.
	if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER)<0) {
			cout << "Failed to initialise SDL: " << SDL_GetError() << endl;
			SDL_Quit();
	}

	// When we close a window quit the SDL application
	atexit(SDL_Quit);

	// Create a new window with an OpenGL surface
	if (!(surf = SDL_SetVideoMode(width, height, colour_depth, SDL_OPENGL))) {
			cout << "Failed to initialise video mode: " << SDL_GetError() << endl;
			SDL_Quit();
	}

	// Initialise GLEW - an easy way to ensure OpenGl 2.0+
	// The *must* be done after we have set the video mode - otherwise we have no OpenGL context.
	glewInit();

	PlaneAsset *ground = new PlaneAsset(1.5, 1.5, 0, "shaders/hello-gl3.f.glsl");
	ground->scale(10.0);
	CubeAsset *cube1 = new CubeAsset(0, 0, 0, "shaders/hello-gl.f.glsl");
	CubeAsset *cube2 = new CubeAsset(0, 3, 0, "shaders/hello-gl.f.glsl");
	CubeAsset *cube3 = new CubeAsset(3, 3, 0, "shaders/hello-gl.f.glsl");
	CubeAsset *cube4 = new CubeAsset(3, 0, 0, "shaders/hello-gl2.f.glsl");
	CubeAsset *cube5 = new CubeAsset(3, 6, 0, "shaders/hello-gl.f.glsl");

	shared_ptr<GameAsset> ground_asset = shared_ptr<GameAsset> (ground);
	assets.push_back(ground_asset);

	shared_ptr<GameAsset> asset1 = shared_ptr<GameAsset> (cube1);
	assets.push_back(asset1);

	shared_ptr<GameAsset> asset2 = shared_ptr<GameAsset> (cube2);
	assets.push_back(asset2);

	shared_ptr<GameAsset> asset3 = shared_ptr<GameAsset> (cube3);
	assets.push_back(asset3);

	shared_ptr<GameAsset> asset4 = shared_ptr<GameAsset> (cube4);
	assets.push_back(asset4);

	shared_ptr<GameAsset> asset5 = shared_ptr<GameAsset> (cube5);
	assets.push_back(asset5);

	// Call the function "display" every delay milliseconds
	SDL_AddTimer(delay, display, NULL);

	// Add the main event loop
	SDL_Event event;
	Matrix4 camera = Camera::getInstance().getCameraM();

	//Camera stuff, to be used later, potentially
	//camera.translation(Vector3(10.0, 10.0, 10.0));
	//Camera::getInstance().setCamera(Matrix4::translation(Vector3(5.0, 5.0, 10.0)));// * Matrix4::translation(Vector3(5.0, 5.0, 10.0)));
	//Camera::getInstance().setCamera(Matrix4::identity());
	//camera * Matrix4::lookAt(asset4::get_pos());// Point3(0.0,5.0,10.0), Point3(0.0,0.0,0.0), Vector3(0.0,0.0,1.0));

	//UserInput *input = new UserInput();

	//input->do_things();

	GLfloat move_speed = 0.2;
	bool rot_up, rot_left, rot_down, rot_right;
	rot_up = false;
	rot_left = false;
	rot_down = false;
	rot_right = false;

	while (SDL_WaitEvent(&event)) {
		  //Does all the debuggy things
		  if(debug_mode){
			  //Hide the cursor from view, if needed
			  SDL_ShowCursor(0);

			  //Throw the mouse to the center of the window
			  SDL_WarpMouse(width/2, height/2);
		  }else{
			  SDL_ShowCursor(1);
		  }

		//Do mousey rotations
		if(rot_up){
			//Commented out because it's not working as it should, yet
			//
			//camera = camera * Matrix4::rotationY(5.0/180.0);
			//Camera::getInstance().setCamera((camera * Matrix4::rotationY(5.0/180.0)));
		}
		if(rot_left){
			//camera = camera * Matrix4::rotationY(-5.0/180.0);
			//Camera::getInstance().setCamera((camera * Matrix4::rotationY(-5.0/180.0)));
		}
		if(rot_down){
			//camera = camera * Matrix4::rotationX(5.0/180.0);
			//Camera::getInstance().setCamera((camera * Matrix4::rotationX(5.0/180.0)));
		}
		if(rot_right){
			//camera = camera * Matrix4::rotationX(-5.0/180.0);
			//Camera::getInstance().setCamera((camera * Matrix4::rotationX(-5.0/180.0)));
		}

		//Check for key events
		switch (event.type) {

			case SDL_MOUSEMOTION:
				//This deals with the various mouse motions
				Sint16 x_move, y_move;
				Sint16 x_ref, y_ref;
				x_ref = 0;
				y_ref = 0;
				x_move = event.motion.xrel;
				y_move = event.motion.yrel;
				//Mouse is not moving
				if(x_move == x_ref){
					rot_left = false;
					rot_right = false;
				}
				if(y_move == y_ref){
					rot_up = false;
					rot_down = false;
				}
				//Mouse moved up
				if(x_move > x_ref){
					rot_left = true;
				}
				//Mouse moved left
				if(x_move < x_ref){
					rot_right = true;
				}
				//Mouse moved down
				if(y_move > y_ref){
					rot_up = true;
				}
				//Mouse moved
				if(y_move < y_ref){
					rot_down = true;

				}
				break;

			case SDL_MOUSEBUTTONUP:
				switch(event.button.button){
					case SDL_BUTTON_LEFT:
						//camera_move = false;
						break;
					case SDL_BUTTON_RIGHT:

						break;
				}
				Camera::getInstance().setCamera(Matrix4::identity());
				break;
			case SDL_MOUSEBUTTONDOWN:
				switch(event.button.button){
					case SDL_BUTTON_LEFT:
						//camera_move = true;
						break;

					case SDL_BUTTON_RIGHT:

						break;
				}
				break;

			case SDL_QUIT:
				SDL_Quit();
				break;

			case SDL_USEREVENT:
				display();
				break;

			case SDL_KEYUP:
				Camera::getInstance().setCamera(Matrix4::identity());
				break;

			case SDL_KEYDOWN:
			  switch(event.key.keysym.sym){

			  	  //set the rot_dir booleans to false
			  	  case SDLK_c:
			  		rot_up = false;
					rot_left = false;
					rot_down = false;
					rot_right = false;
			  		break;

			  	  //Movement of one of the cubes
			  	  //Move cube up
			  	  case SDLK_y:
			  		  cube4->move(4);
			  		  break;
			  	  //Move cube down
				  case SDLK_h:
					  cube4->move(5);
					  break;
				  //Move cube forward
				  case SDLK_i:
					  cube4->move(0);
					  break;
				  //Move cube left
				  case SDLK_j:
					  cube4->move(1);
					  break;
				  //Move cube backward
				  case SDLK_k:
					  cube4->move(2);
					  break;
				  //Move cube right
				  case SDLK_l:
					  cube4->move(3);
					  break;

				  //Switches between debug/normal modes
				  case SDLK_q:
					  if(debug_mode){
						  debug_mode = false;
					  }else{
						  debug_mode = true;
					  }
					  break;

				  //Camera movement
				  //Rotate up
				  case SDLK_w:
					  cout << "w pressed" << endl << std::flush;
					  Camera::getInstance().setCamera((camera * Matrix4::rotationX(5.0/180.0)));
					  //input->move_camera(0);
					  break;
				  //Rotate left
				  case SDLK_a:
					  Camera::getInstance().setCamera((camera * Matrix4::rotationY(5.0/180.0)));
					  break;
				  //Rotate down
				  case SDLK_s:
					  Camera::getInstance().setCamera((camera * Matrix4::rotationX(-5.0/180.0)));
					  break;
				  //Rotate right
				  case SDLK_d:
					  Camera::getInstance().setCamera((camera * Matrix4::rotationY(-5.0/180.0)));
					  break;
				  //Move left
				  case SDLK_LEFT:
					Camera::getInstance().setCamera(camera * Matrix4::translation(Vector3(move_speed, 0.0, 0.0)) );
					break;
				  //Move right
				  case SDLK_RIGHT:
					Camera::getInstance().setCamera(camera * Matrix4::translation(Vector3(-move_speed, 0.0, 0.0)) );
					break;
				  //Move forward
				  case SDLK_UP:
					Camera::getInstance().setCamera(camera * Matrix4::translation(Vector3(0.0, 0.0, -move_speed)) );
					break;
				  //Move backward
				  case SDLK_DOWN:
					Camera::getInstance().setCamera(camera * Matrix4::translation(Vector3(0.0, 0.0, move_speed)) );
					break;
				  //Move up
					case SDLK_SPACE:
					Camera::getInstance().setCamera(camera * Matrix4::translation(Vector3(0.0, -move_speed, 0.0)) );
					break;
				  //Move down
					case SDLK_LSHIFT:
					Camera::getInstance().setCamera(camera * Matrix4::translation(Vector3(0.0, move_speed, 0.0)) );
					break;

				  //Ends the program
				  case SDLK_ESCAPE:
					  SDL_Quit();
					  break;
				  default:
					break;
				  }
			  break;
			}
	}
	return 0;
}
